package cn.phecda.boot.common.lang.util;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
 * <p> Json工具 </p>
 *
 * @author : wanpinwei
 */
@Slf4j
public final class JacksonUtils {
    private final byte[] lock = new byte[0];

    private ObjectMapper objectMapper;

    private static final JacksonUtils INSTANCE = new JacksonUtils();

    private ObjectMapper getObjectMapper() {
        if (null != objectMapper) {
            return objectMapper;
        }

        synchronized (lock) {
            if (ObjectUtils.isNotEmpty(objectMapper)) {
                return objectMapper;
            }

            try {
                objectMapper = SpringUtil.getBean(ObjectMapper.class);
            } catch (Exception ex) {
                log.warn("|Phecda Boot Common|- Get 'ObjectMapper' bean from ApplicationContext fail [message={}] ", ex.getMessage());

                objectMapper = new ObjectMapper();
            }
        }
        return objectMapper;
    }

    @SneakyThrows
    public static <T> String toJson(T domain) {
        return INSTANCE.getObjectMapper().writeValueAsString(domain);
    }

    public static TypeFactory getTypeFactory() {
        return INSTANCE.getObjectMapper().getTypeFactory();
    }

    @SneakyThrows
    public static <T> T toBean(String content, Class<T> valueType) {
        return INSTANCE.getObjectMapper().readValue(content, valueType);
    }

    public static <T> T toBean(Map<String, Object> content, Class<T> valueType) {
        return INSTANCE.getObjectMapper().convertValue(content, valueType);
    }

    @SneakyThrows
    public static <T> T toBean(String content, TypeReference<T> typeReference) {
        return INSTANCE.getObjectMapper().readValue(content, typeReference);
    }

    @SneakyThrows
    public static <T> T toBean(String content, JavaType javaType) {
        return INSTANCE.getObjectMapper().readValue(content, javaType);
    }

    public static <T> List<T> toList(String content, Class<T> clazz) {
        JavaType javaType = INSTANCE.getObjectMapper().getTypeFactory().constructParametricType(List.class, clazz);
        return toBean(content, javaType);
    }

    public static <K, V> Map<K, V> toMap(String content, Class<K> keyClass, Class<V> valueClass) {
        JavaType javaType = INSTANCE.getObjectMapper().getTypeFactory().constructMapType(Map.class, keyClass, valueClass);
        return toBean(content, javaType);
    }

    public static Map<String, Object> toMap(String content) {
        return toMap(content, String.class, Object.class);
    }

    public static <T> Set<T> toSet(String content, Class<T> clazz) {
        JavaType javaType = getTypeFactory().constructCollectionLikeType(Set.class, clazz);
        return toBean(content, javaType);
    }

    public static <T> T[] toArray(String content, Class<T> clazz) {
        JavaType javaType = getTypeFactory().constructArrayType(clazz);
        return toBean(content, javaType);
    }

    public static <T> T[] toArray(String content) {
        return toBean(content, new TypeReference<T[]>() {
        });
    }

    @SneakyThrows
    public static JsonNode toNode(String content) {
        return INSTANCE.getObjectMapper().readTree(content);
    }

    @SneakyThrows
    public static JsonNode toNode(JsonParser jsonParser) {
        return INSTANCE.getObjectMapper().readTree(jsonParser);
    }

    @SneakyThrows
    public static JsonParser createParser(String content) {
        return INSTANCE.getObjectMapper().createParser(content);
    }

    public static <R> R loop(JsonNode jsonNode, Function<JsonNode, R> function) {
        if (jsonNode.isObject()) {
            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                loop(entry.getValue(), function);
            }
        }

        if (jsonNode.isArray()) {
            for (JsonNode node : jsonNode) {
                loop(node, function);
            }
        }

        if (jsonNode.isValueNode()) {
            return function.apply(jsonNode);
        } else {
            return null;
        }
    }
}
